# Copyright 2010 Mikko Rantanen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import httplib
import simplejson as json
import re

import logging

def _aspnet_url(path):
    m = re.match('(?P<path>/[^?]*)(?P<query>\?.*)?', path)

    ext = m.group('path')[-5:]
    aspmatch = re.match('\.as[ph]x', ext)
    if aspmatch:
        return path
    else:
        extendedpath = path + '.ashx'
        if m.group('query'):
            extendedpath = extendedpath + m.group('query')

        return extendedpath

def _combine(path1, path2):
    r1 = path1[-1] == '/' and -1 or None
    r2 = path2[0] == '/' and 1 or 0

    return path1[:r1] + '/' + path2[r2:]

class Resource():
    """ Type for a single REST resource

    A covenience type for modeling the REST interface in an object
    oriented manner.
    """

    def __init__(self, url, conn = None):

        m = re.match('(?:(?P<protocol>https?)://)?(?P<host>[^/]*)(?P<path>/.*)?', url)

        if not m:
            # TODO: Raise exception
            pass

        self.url = url
        self.path = m.group('path')
        self.aspnetpath = _aspnet_url(self.path)

        if not conn:
            self.conn = ServiceConnection(m.group('protocol'), m.group('host'))
        else:
            self.conn = conn

    def get(self, path = None, args = None, **kw):
        path = self._get_path(path)
        return self.conn.request(path, 'GET', None, args, **kw)

    def delete(self, path = None, args = None, **kw):
        path = self._get_path(path)
        return self.conn.request(path, 'DELETE', data, args, **kw)

    def post(self, data, path = None, args = None, **kw):
        path = self._get_path(path)
        return self.conn.request(path, 'POST', data, args, **kw)

    def put(self, data, path = None, args = None, **kw):
        path = self._get_path(path)
        return self.conn.request(path, 'PUT', data, args, **kw)

    def resource(self, *path):
        respath = '/'.join(map(lambda x: str(x), path))
        return Resource(_combine(self.url, respath), self.conn)

    def init_cookies(self, path = None):
        if path:
            path = _combine(self.path, path)
        else:
            path = self.path
        self.conn.init_cookies(path)

    def _get_path(self, path):
        if not path:
            return self.aspnetpath
        else:
            return _aspnet_url(_combine(self.path, path))



class ServiceConnection():

    def __init__(self, protocol, host):

        if protocol == 'http':
            self.conn_type = httplib.HTTPConnection
        elif protocol == 'https':
            self.conn_type = httplib.HTTPSConnection
        else:
            # TODO: Raise exception
            pass

        self.host = host
        self.cookies = {}

    def request(self, path, method, data, args, **kw):

        if data:
            data = json.dumps(data)

        if args:
            path = path + '?' + '&'.join(map(lambda (k,v): k + '=' + v, args.items()))

        path, method = self._tweak_method(path, method)

        headers = { 'Content-Type':'text/json' }
        cookies = []
        for key, value in self.cookies.items():
            cookies.append(key + "=" + value)
        
        headers['Cookie'] = '; '.join(cookies)
        
        
        logging.debug('Performing %s on %s' % (method, _combine(self.host, path)))
        conn = self.conn_type(self.host)
        conn.request(method, path, data, headers)

        response = conn.getresponse()

        responsefile = kw.get('file')
        if responsefile:
            while True:
                buffer = response.read(8192)
                responsefile.write(buffer)

                if len(buffer) == 0:
                    break

            logging.debug('Wrote response to disk')
            return responsefile
        else:
            content = response.read()
            try:
                retval = json.loads(content)
                logging.debug('Response deserialized')
                return retval
            except:
                print content
                raise

    def init_cookies(self, path):

        conn = self.conn_type(self.host)
        conn.request('GET', path)

        response = conn.getresponse()

        retrieved_cookies = response.msg.getallmatchingheaders('set-cookie')
        for cookie in retrieved_cookies:
            m = re.search('^Set-Cookie: (?P<key>[^=]*)=(?P<value>[^;]*)', cookie)
            if m:
                self.cookies[m.group('key')] = m.group('value')

        logging.debug('%i cookies retrieved from %s' %
                     (len(retrieved_cookies),_combine(self.host, path)))

    def _tweak_method(self, uri_path, method):

        # Append the real http method at the end of the uri since IIS
        # often blocks non-GET and POST requests.
        if method != 'GET' and method != 'POST':
            if uri_path.rfind('?') == -1:
                uri_path = uri_path + '?'
            else:
                uri_path = uri_path + '&'

            uri_path = uri_path + '_method=' + method
            method = 'POST'

        return uri_path, method

